<!DOCTYPE html>
<html>
<head>
<title>函数式编程.md</title>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">

<style>
/* https://github.com/microsoft/vscode/blob/master/extensions/markdown-language-features/media/markdown.css */
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

body {
	font-family: var(--vscode-markdown-font-family, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif);
	font-size: var(--vscode-markdown-font-size, 14px);
	padding: 0 26px;
	line-height: var(--vscode-markdown-line-height, 22px);
	word-wrap: break-word;
}

#code-csp-warning {
	position: fixed;
	top: 0;
	right: 0;
	color: white;
	margin: 16px;
	text-align: center;
	font-size: 12px;
	font-family: sans-serif;
	background-color:#444444;
	cursor: pointer;
	padding: 6px;
	box-shadow: 1px 1px 1px rgba(0,0,0,.25);
}

#code-csp-warning:hover {
	text-decoration: none;
	background-color:#007acc;
	box-shadow: 2px 2px 2px rgba(0,0,0,.25);
}

body.scrollBeyondLastLine {
	margin-bottom: calc(100vh - 22px);
}

body.showEditorSelection .code-line {
	position: relative;
}

body.showEditorSelection .code-active-line:before,
body.showEditorSelection .code-line:hover:before {
	content: "";
	display: block;
	position: absolute;
	top: 0;
	left: -12px;
	height: 100%;
}

body.showEditorSelection li.code-active-line:before,
body.showEditorSelection li.code-line:hover:before {
	left: -30px;
}

.vscode-light.showEditorSelection .code-active-line:before {
	border-left: 3px solid rgba(0, 0, 0, 0.15);
}

.vscode-light.showEditorSelection .code-line:hover:before {
	border-left: 3px solid rgba(0, 0, 0, 0.40);
}

.vscode-light.showEditorSelection .code-line .code-line:hover:before {
	border-left: none;
}

.vscode-dark.showEditorSelection .code-active-line:before {
	border-left: 3px solid rgba(255, 255, 255, 0.4);
}

.vscode-dark.showEditorSelection .code-line:hover:before {
	border-left: 3px solid rgba(255, 255, 255, 0.60);
}

.vscode-dark.showEditorSelection .code-line .code-line:hover:before {
	border-left: none;
}

.vscode-high-contrast.showEditorSelection .code-active-line:before {
	border-left: 3px solid rgba(255, 160, 0, 0.7);
}

.vscode-high-contrast.showEditorSelection .code-line:hover:before {
	border-left: 3px solid rgba(255, 160, 0, 1);
}

.vscode-high-contrast.showEditorSelection .code-line .code-line:hover:before {
	border-left: none;
}

img {
	max-width: 100%;
	max-height: 100%;
}

a {
	text-decoration: none;
}

a:hover {
	text-decoration: underline;
}

a:focus,
input:focus,
select:focus,
textarea:focus {
	outline: 1px solid -webkit-focus-ring-color;
	outline-offset: -1px;
}

hr {
	border: 0;
	height: 2px;
	border-bottom: 2px solid;
}

h1 {
	padding-bottom: 0.3em;
	line-height: 1.2;
	border-bottom-width: 1px;
	border-bottom-style: solid;
}

h1, h2, h3 {
	font-weight: normal;
}

table {
	border-collapse: collapse;
}

table > thead > tr > th {
	text-align: left;
	border-bottom: 1px solid;
}

table > thead > tr > th,
table > thead > tr > td,
table > tbody > tr > th,
table > tbody > tr > td {
	padding: 5px 10px;
}

table > tbody > tr + tr > td {
	border-top: 1px solid;
}

blockquote {
	margin: 0 7px 0 5px;
	padding: 0 16px 0 10px;
	border-left-width: 5px;
	border-left-style: solid;
}

code {
	font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback";
	font-size: 1em;
	line-height: 1.357em;
}

body.wordWrap pre {
	white-space: pre-wrap;
}

pre:not(.hljs),
pre.hljs code > div {
	padding: 16px;
	border-radius: 3px;
	overflow: auto;
}

pre code {
	color: var(--vscode-editor-foreground);
	tab-size: 4;
}

/** Theming */

.vscode-light pre {
	background-color: rgba(220, 220, 220, 0.4);
}

.vscode-dark pre {
	background-color: rgba(10, 10, 10, 0.4);
}

.vscode-high-contrast pre {
	background-color: rgb(0, 0, 0);
}

.vscode-high-contrast h1 {
	border-color: rgb(0, 0, 0);
}

.vscode-light table > thead > tr > th {
	border-color: rgba(0, 0, 0, 0.69);
}

.vscode-dark table > thead > tr > th {
	border-color: rgba(255, 255, 255, 0.69);
}

.vscode-light h1,
.vscode-light hr,
.vscode-light table > tbody > tr + tr > td {
	border-color: rgba(0, 0, 0, 0.18);
}

.vscode-dark h1,
.vscode-dark hr,
.vscode-dark table > tbody > tr + tr > td {
	border-color: rgba(255, 255, 255, 0.18);
}

</style>

<style>
/* Tomorrow Theme */
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
/* Original theme - https://github.com/chriskempson/tomorrow-theme */

/* Tomorrow Comment */
.hljs-comment,
.hljs-quote {
	color: #8e908c;
}

/* Tomorrow Red */
.hljs-variable,
.hljs-template-variable,
.hljs-tag,
.hljs-name,
.hljs-selector-id,
.hljs-selector-class,
.hljs-regexp,
.hljs-deletion {
	color: #c82829;
}

/* Tomorrow Orange */
.hljs-number,
.hljs-built_in,
.hljs-builtin-name,
.hljs-literal,
.hljs-type,
.hljs-params,
.hljs-meta,
.hljs-link {
	color: #f5871f;
}

/* Tomorrow Yellow */
.hljs-attribute {
	color: #eab700;
}

/* Tomorrow Green */
.hljs-string,
.hljs-symbol,
.hljs-bullet,
.hljs-addition {
	color: #718c00;
}

/* Tomorrow Blue */
.hljs-title,
.hljs-section {
	color: #4271ae;
}

/* Tomorrow Purple */
.hljs-keyword,
.hljs-selector-tag {
	color: #8959a8;
}

.hljs {
	display: block;
	overflow-x: auto;
	color: #4d4d4c;
	padding: 0.5em;
}

.hljs-emphasis {
	font-style: italic;
}

.hljs-strong {
	font-weight: bold;
}
</style>

<style>
/*
 * Markdown PDF CSS
 */

 body {
	font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif, "Meiryo";
	padding: 0 12px;
}

pre {
	background-color: #f8f8f8;
	border: 1px solid #cccccc;
	border-radius: 3px;
	overflow-x: auto;
	white-space: pre-wrap;
	overflow-wrap: break-word;
}

pre:not(.hljs) {
	padding: 23px;
	line-height: 19px;
}

blockquote {
	background: rgba(127, 127, 127, 0.1);
	border-color: rgba(0, 122, 204, 0.5);
}

.emoji {
	height: 1.4em;
}

code {
	font-size: 14px;
	line-height: 19px;
}

/* for inline code */
:not(pre):not(.hljs) > code {
	color: #C9AE75; /* Change the old color so it seems less like an error */
	font-size: inherit;
}

/* Page Break : use <div class="page"/> to insert page break
-------------------------------------------------------- */
.page {
	page-break-after: always;
}

</style>

<script src="https://unpkg.com/mermaid/dist/mermaid.min.js"></script>
</head>
<body>
  <script>
    mermaid.initialize({
      startOnLoad: true,
      theme: document.body.classList.contains('vscode-dark') || document.body.classList.contains('vscode-high-contrast')
          ? 'dark'
          : 'default'
    });
  </script>
<h1 id="%E7%9B%AE%E5%BD%95">目录</h1>
<p>[TOC]</p>
<h2 id="%E8%AF%BE%E7%A8%8B%E4%BB%8B%E7%BB%8D">课程介绍</h2>
<ul>
<li>为什么要学习函数编程以及什么是函数编程</li>
<li>函数式编程的特性（纯函数、柯里化、函数组合等）</li>
<li>函数式编程的应用场景</li>
<li>函数式编程库Lodash</li>
</ul>
<h2 id="%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E5%AD%A6%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B">为什么要学函数式编程？</h2>
<p>函数式编程是一个非常古老的概念。</p>
<ul>
<li>函数式表成是随着React的流行收到越来越多的关注（React的高阶组件使用了高阶函数来实现，高阶函数就是函数式编程的一个特性。Redux也使用了函数式编程的思想。）</li>
<li>Vue3也开始拥抱函数式编程</li>
<li>函数式编程可以抛弃this</li>
<li>打包过程中可以更好的利用tree shaking过滤无用代码</li>
<li>方便测试、方便并行处理</li>
<li>有很多库可以帮助我们进行函数式开发：lodash、underscore、ramda</li>
</ul>
<h2 id="%E4%BB%80%E4%B9%88%E6%98%AF%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B">什么是函数式编程？</h2>
<p>函数式编程，缩写FP，是一种编程范式，也是一种编程风格，和面向对象是并列的关系。函数式编程我们可以认为是一种思维模式，加上实现方法。其思维方式就是把现实世界事物和事物之间的联系抽象到程序世界（是对运算过程进行抽象）</p>
<p>常听说的编程范式还有面向过程编程（按照步骤来实现）、面向对象编程（把现实中的事物抽象成类和对象，通过封装、继承和多态来演示不同事物之间的联系）。</p>
<h3 id="%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%E5%92%8C%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%BC%96%E7%A8%8B%E7%9A%84%E4%B8%8D%E5%90%8C">函数式编程和面向对象编程的不同</h3>
<ul>
<li>从思维方式上来说
面向对象编程是对事物的抽象，而函数式编程是对运算过程的抽象</li>
</ul>
<h3 id="%E5%AF%B9%E4%BA%8E%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%E6%80%9D%E7%BB%B4%E6%96%B9%E5%BC%8F%E7%9A%84%E7%90%86%E8%A7%A3">对于函数式编程思维方式的理解：</h3>
<ul>
<li>程序的本质：根据输入通过某种运算获得相应的输出，程序开发过程中会涉及很多输入和输出的函数。</li>
<li>函数式编程中的函数指的不是程序中的函数Function，而是数学中的函数即映射关系，例如：y=sin(x)，是这种x和y的关系</li>
<li>相同的输入时钟要得到相同的输出（纯函数）</li>
<li>函数式编程用描述数据（函数）之间的映射</li>
</ul>
<pre class="hljs"><code><div><span class="hljs-comment">// 非函数式</span>
<span class="hljs-keyword">let</span> num1 = <span class="hljs-number">2</span>
<span class="hljs-keyword">let</span> num2 = <span class="hljs-number">3</span>
<span class="hljs-keyword">let</span> sum = num1 + num2
<span class="hljs-built_in">console</span>.log(sum)

<span class="hljs-comment">// 函数式</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">add</span>(<span class="hljs-params">n1, n2</span>) </span>{
    <span class="hljs-keyword">return</span> n1 + n2
}
<span class="hljs-keyword">let</span> sum = add(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>)
<span class="hljs-built_in">console</span>.log(sum)
</div></code></pre>
<h2 id="%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%E7%9A%84%E5%89%8D%E7%BD%AE%E7%9F%A5%E8%AF%86">函数式编程的前置知识</h2>
<h3 id="%E5%87%BD%E6%95%B0%E6%98%AF%E4%B8%80%E7%AD%89%E5%85%AC%E6%B0%91">函数是一等公民</h3>
<p>在JS中函数就是一个普通的对象，我们可以把函数存储到变量/数组中，它还可以作为另一个函数的参数和返回值，甚至我们可以在程序运行的时候通过<code>new Function('alert(1)')</code>来构造一个新的函数。</p>
<ul>
<li>函数可以存储在变量中</li>
</ul>
<pre class="hljs"><code><div><span class="hljs-comment">// 把函数赋值给变量</span>
<span class="hljs-keyword">let</span> fn = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"hi"</span>)
}

fn()

<span class="hljs-comment">// 一个示例</span>
<span class="hljs-keyword">const</span> BlogController = {
    index (posts) { <span class="hljs-keyword">return</span> Views.index(posts) },
    show (post) { <span class="hljs-keyword">return</span> Views.show(post) },
    create (attrs) { <span class="hljs-keyword">return</span> Db.create(attrs) },
    update (post, attrs) { <span class="hljs-keyword">return</span> Db.update(post, attrs) },
    destroy (post) { <span class="hljs-keyword">return</span> Db.destroy(post) }
}

<span class="hljs-comment">// 优化 赋值的是Views的index方法，不是方法的调用</span>
<span class="hljs-keyword">const</span> BlogController = {
    <span class="hljs-attr">index</span>: Views.index,
    <span class="hljs-attr">show</span>: Views.show,
    <span class="hljs-attr">create</span>: Db.create,
    <span class="hljs-attr">update</span>: Db.update,
    <span class="hljs-attr">destroy</span>: Db.destroy
}    
</div></code></pre>
<p>下面两个特性在高阶函数中会有详细说明</p>
<ul>
<li>函数可以作为参数</li>
<li>函数可以作为返回值</li>
</ul>
<h3 id="%E9%AB%98%E9%98%B6%E5%87%BD%E6%95%B0">高阶函数</h3>
<h4 id="%E4%BB%80%E4%B9%88%E6%98%AF%E9%AB%98%E9%98%B6%E5%87%BD%E6%95%B0">什么是高阶函数？</h4>
<p>高阶函数（Higher-order function）</p>
<ul>
<li>函数可以作为参数</li>
</ul>
<pre class="hljs"><code><div><span class="hljs-comment">// forEach</span>
<span class="hljs-comment">// 定义一个遍历数组的并对每一项做处理的函数，第一个函数是一个数组，第二个参数是一个函数。</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">forEach</span> (<span class="hljs-params">array, fn</span>) </span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; array.length; i++) {
        fn(array[i]) 
    } 
}

<span class="hljs-comment">// test</span>
<span class="hljs-keyword">let</span> arr = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>]
forEach(arr, item =&gt; {
    item = item * <span class="hljs-number">2</span>
    <span class="hljs-built_in">console</span>.log(item) <span class="hljs-comment">// 2 4 6</span>
})
</div></code></pre>
<pre class="hljs"><code><div><span class="hljs-comment">// filter</span>
<span class="hljs-comment">// 遍历数组，并把满足条件的元素存储成数组，再进行返回</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">filter</span>(<span class="hljs-params">array, fn</span>) </span>{
    <span class="hljs-keyword">let</span> results = []
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; array.length; i++) { 
        <span class="hljs-comment">//如果满足条件  </span>
        <span class="hljs-keyword">if</span> (fn(array[i])) { 
            results.push(array[i]) 
        }    
    }
    <span class="hljs-keyword">return</span> results
}

<span class="hljs-comment">// test</span>
<span class="hljs-keyword">let</span> arr = [<span class="hljs-number">1</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">7</span>, <span class="hljs-number">8</span>]
<span class="hljs-keyword">let</span> result = filter(arr, item =&gt; item % <span class="hljs-number">2</span> === <span class="hljs-number">0</span>)
<span class="hljs-built_in">console</span>.log(result) <span class="hljs-comment">// [4, 8]</span>
</div></code></pre>
<ul>
<li>函数作为返回值</li>
</ul>
<pre class="hljs"><code><div><span class="hljs-comment">// 一个函数返回另一个函数</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">makeFn</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">let</span> msg = <span class="hljs-string">'Hello function'</span> 
    <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{ 
        <span class="hljs-built_in">console</span>.log(msg) 
    } 
}

<span class="hljs-comment">// test</span>
<span class="hljs-comment">// 第一种调用方式</span>
<span class="hljs-keyword">const</span> fn = makeFn() 
fn() <span class="hljs-comment">//Hello function</span>

<span class="hljs-comment">// 第二种调用方式</span>
makeFn()()<span class="hljs-comment">///Hello function</span>
</div></code></pre>
<pre class="hljs"><code><div><span class="hljs-comment">// once</span>
<span class="hljs-comment">// 让函数只执行一次</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">once</span>(<span class="hljs-params">fn</span>) </span>{
    <span class="hljs-keyword">let</span> done = <span class="hljs-literal">false</span>
    <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-comment">// 判断值有没有被执行，如果是false表示没有执行，如果是true表示已经执行过了，不必再执行</span>
        <span class="hljs-keyword">if</span>(!done) {
            done = <span class="hljs-literal">true</span>
            <span class="hljs-comment">// 调用fn，当前this直接传递过来，第二个参数是把fn的参数传递给return的函数</span>
            <span class="hljs-keyword">return</span> fn.apply(<span class="hljs-keyword">this</span>, <span class="hljs-built_in">arguments</span>)
        }
    }
}

<span class="hljs-comment">// test</span>
<span class="hljs-keyword">let</span> pay = once(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">money</span>) </span>{
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`支付：<span class="hljs-subst">${money}</span> RMB`</span>)
})

pay(<span class="hljs-number">5</span>) <span class="hljs-comment">//支付：5 RMB</span>
pay(<span class="hljs-number">5</span>)
pay(<span class="hljs-number">5</span>)
pay(<span class="hljs-number">5</span>)
pay(<span class="hljs-number">5</span>)
</div></code></pre>
<h4 id="%E4%BD%BF%E7%94%A8%E9%AB%98%E9%98%B6%E5%87%BD%E6%95%B0%E7%9A%84%E6%84%8F%E4%B9%89">使用高阶函数的意义</h4>
<ul>
<li>抽象可以帮我们屏蔽细节，我们只需要知道我们的目标和解决这类问题的函数，我们不需要关心实现的细节</li>
<li>高阶函数是用来抽象通用的问题</li>
</ul>
<h4 id="%E5%B8%B8%E7%94%A8%E7%9A%84%E9%AB%98%E9%98%B6%E5%87%BD%E6%95%B0">常用的高阶函数</h4>
<p>有一个通用的特点，就是需要一个函数作为参数。</p>
<ul>
<li>forEach</li>
<li>map
对数组中的每个元素进行遍历，并处理，处理的结果放在一个新数组中返回</li>
</ul>
<pre class="hljs"><code><div><span class="hljs-keyword">const</span> map = <span class="hljs-function">(<span class="hljs-params">array, fn</span>) =&gt;</span> { 
    <span class="hljs-keyword">let</span> results = [] 
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> value <span class="hljs-keyword">of</span> array) { 
        results.push(fn(value)) 
    }
    <span class="hljs-keyword">return</span> results 
}

<span class="hljs-comment">// test</span>
<span class="hljs-keyword">let</span> arr = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>]
arr = map(arr, v =&gt; v * v)
<span class="hljs-built_in">console</span>.log(arr)
<span class="hljs-comment">//</span>
</div></code></pre>
<ul>
<li>filter</li>
<li>every
数组中的每一个元素是否都匹配我们指定的一个条件，如果都满足返回true，如果不满足返回false</li>
</ul>
<pre class="hljs"><code><div><span class="hljs-keyword">const</span> every = <span class="hljs-function">(<span class="hljs-params">array, fn</span>) =&gt;</span> { 
    <span class="hljs-keyword">let</span> result = <span class="hljs-literal">true</span> 
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> value <span class="hljs-keyword">of</span> array) {
        result = fn(value) 
        <span class="hljs-comment">// 如果有一个元素不满足就直接跳出循环</span>
        <span class="hljs-keyword">if</span> (!result) { 
            <span class="hljs-keyword">break</span> 
        }
    }
    <span class="hljs-keyword">return</span> result
}

<span class="hljs-comment">// test</span>
<span class="hljs-keyword">let</span> arr = [<span class="hljs-number">11</span>, <span class="hljs-number">12</span>, <span class="hljs-number">14</span>]
<span class="hljs-keyword">let</span> r = every(arr, v =&gt; v &gt; <span class="hljs-number">10</span>)
<span class="hljs-built_in">console</span>.log(r) <span class="hljs-comment">// false</span>

r = every(arr, v =&gt; v &gt; <span class="hljs-number">12</span>)
<span class="hljs-built_in">console</span>.log(r) <span class="hljs-comment">// false</span>
</div></code></pre>
<ul>
<li>some
判断数组中是否有一个元素满足我们指定的条件，满足是true，都不满足为false</li>
</ul>
<pre class="hljs"><code><div><span class="hljs-keyword">const</span> some = <span class="hljs-function">(<span class="hljs-params">array, fn</span>) =&gt;</span> { 
    <span class="hljs-keyword">let</span> result = <span class="hljs-literal">false</span> 
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> value <span class="hljs-keyword">of</span> array) {
        result = fn(value) 
        <span class="hljs-comment">// 如果有一个元素不满足就直接跳出循环</span>
        <span class="hljs-keyword">if</span> (result) { 
            <span class="hljs-keyword">break</span> 
        }
    }
    <span class="hljs-keyword">return</span> result
}

<span class="hljs-comment">// test</span>
<span class="hljs-keyword">let</span> arr = [<span class="hljs-number">1</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">9</span>]
<span class="hljs-keyword">let</span> arr1 = [<span class="hljs-number">1</span>, <span class="hljs-number">3</span>, <span class="hljs-number">5</span>, <span class="hljs-number">9</span>]
<span class="hljs-keyword">let</span> r = some(arr, v =&gt; v % <span class="hljs-number">2</span> === <span class="hljs-number">0</span>)
<span class="hljs-built_in">console</span>.log(r) <span class="hljs-comment">// true</span>
r = some(arr1, v =&gt; v % <span class="hljs-number">2</span> === <span class="hljs-number">0</span>)
<span class="hljs-built_in">console</span>.log(r) <span class="hljs-comment">// false</span>

</div></code></pre>
<ul>
<li>find/findIndex</li>
<li>reduce</li>
<li>sort</li>
</ul>
<h3 id="%E9%97%AD%E5%8C%85">闭包</h3>
<h4 id="%E9%97%AD%E5%8C%85%E7%9A%84%E6%A6%82%E5%BF%B5">闭包的概念</h4>
<p>闭包：函数和其周围的状态（词法环境）的引用捆绑在一起形成闭包</p>
<ul>
<li>可以在另一个作用域中调用一个函数的内部函数并访问到该函数作用域中的成员</li>
</ul>
<p>在上面函数作为返回值的过程中，其实我们就用到了闭包，下面进行语法演示：</p>
<pre class="hljs"><code><div><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">makeFn</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">let</span> msg = <span class="hljs-string">'Hello function'</span>
}
<span class="hljs-comment">// 正常情况下，执行完makeFn，里面的变量msg会释放掉</span>
<span class="hljs-comment">// 但是下面的情况</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">makeFn</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">let</span> msg = <span class="hljs-string">'Hello function'</span>
    <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{ 
        <span class="hljs-built_in">console</span>.log(msg)
    } 
}
<span class="hljs-comment">// 在上面函数中，返回了一个函数，而且在函数中还访问了原来函数内部的成员，就可以称为闭包</span>

<span class="hljs-keyword">const</span> fn = makeFn()
fn()
<span class="hljs-comment">// fn为外部函数，当外部函数对内部成员有引用的时候，那么内部的成员msg就不能被释放。当我们调用fn的时候，我们就会访问到msg。</span>

<span class="hljs-comment">//注意的点：</span>
<span class="hljs-comment">//1、我们可以在另一个作用域调用makeFn的内部函数</span>
<span class="hljs-comment">//2、当我们调用内部函数的时候我们可以访问到内部成员</span>

</div></code></pre>
<h4 id="%E9%97%AD%E5%8C%85%E7%9A%84%E6%A0%B8%E5%BF%83%E4%BD%9C%E7%94%A8">闭包的核心作用</h4>
<p>把函数内部成员的作用范围延长</p>
<h4 id="%E9%97%AD%E5%8C%85%E7%9A%84%E6%9C%AC%E8%B4%A8">闭包的本质</h4>
<p>函数在执行的时候会放到一个执行栈上，当函数执行完毕之后会从执行栈上移除。但是堆上的作用域成员因为被外部引用不能释放，因此内部函数依然可以访问外部函数的成员。</p>
<p>/<em>解读：函数执行的时候在执行栈上，执行完毕之后从执行栈上移除，内部成员的内存被释放。但是在函数执行完毕移除之后，释放内存的时候，如果外部有引用，则内部成员的内存不能被释放。</em>/</p>
<h4 id="%E9%97%AD%E5%8C%85%E7%9A%84%E6%A1%88%E4%BE%8B">闭包的案例</h4>
<h5 id="%E6%A1%88%E4%BE%8B%E4%B8%80">案例一</h5>
<p>计算一个数平方和立方的运算</p>
<pre class="hljs"><code><div><span class="hljs-built_in">Math</span>.pow(<span class="hljs-number">4</span>, <span class="hljs-number">2</span>)
<span class="hljs-built_in">Math</span>.pow(<span class="hljs-number">5</span>, <span class="hljs-number">2</span>)
<span class="hljs-comment">// 后面的二次方三次方很多次重复，下面要写一个二次方三次方的函数</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">makePower</span> (<span class="hljs-params">power</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">number</span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">Math</span>.pow(number, power)
  }
}

<span class="hljs-comment">// 求平方</span>
<span class="hljs-keyword">let</span> power2 = makePower(<span class="hljs-number">2</span>)
<span class="hljs-keyword">let</span> power3 = makePower(<span class="hljs-number">3</span>)

<span class="hljs-built_in">console</span>.log(power2(<span class="hljs-number">4</span>)) <span class="hljs-comment">// 16</span>
<span class="hljs-built_in">console</span>.log(power2(<span class="hljs-number">5</span>)) <span class="hljs-comment">// 25</span>
<span class="hljs-built_in">console</span>.log(power3(<span class="hljs-number">4</span>)) <span class="hljs-comment">// 64</span>
</div></code></pre>
<p>调试台的案例演示</p>
<h5 id="%E6%A1%88%E4%BE%8B%E4%BA%8C">案例二</h5>
<p>计算不同级别的员工工资</p>
<pre class="hljs"><code><div><span class="hljs-comment">// 假设计算员工工资的函数第一个函数传基本工资，第二个参数传绩效工资</span>
<span class="hljs-comment">// getSalary(12000, 2000)</span>
<span class="hljs-comment">// getSalary(15000, 3000)</span>
<span class="hljs-comment">// getSalary(15000, 4000)</span>

<span class="hljs-comment">// 不同级别的员工基本工资是一样的，所以我们将基本工资提取出来，之后只需要加上绩效工资</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">makeSalary</span> (<span class="hljs-params">base</span>) </span>{ 
    <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">performance</span>) </span>{ 
        <span class="hljs-keyword">return</span> base + performance 
    }
}
<span class="hljs-keyword">let</span> salaryLevel1 = makeSalary(<span class="hljs-number">12000</span>)
<span class="hljs-keyword">let</span> salaryLevel2 = makeSalary(<span class="hljs-number">15000</span>)

<span class="hljs-built_in">console</span>.log(salaryLevel1(<span class="hljs-number">2000</span>)) <span class="hljs-comment">//14000</span>
<span class="hljs-built_in">console</span>.log(salaryLevel2(<span class="hljs-number">3000</span>)) <span class="hljs-comment">//18000</span>
<span class="hljs-built_in">console</span>.log(salaryLevel2(<span class="hljs-number">4000</span>)) <span class="hljs-comment">//19000</span>
</div></code></pre>
<h2 id="%E7%BA%AF%E5%87%BD%E6%95%B0">纯函数</h2>
<h3 id="%E7%BA%AF%E5%87%BD%E6%95%B0%E7%9A%84%E6%A6%82%E5%BF%B5">纯函数的概念</h3>
<p><strong>相同的输入永远会得到相同的输出</strong>，而且没有任何可观察的副作用。</p>
<p>纯函数就类似数学中的函数(用来描述输入和输出之间的关系)，y = f(x)</p>
<pre class="hljs"><code><div><span class="hljs-keyword">let</span> numbers = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>] 
<span class="hljs-comment">// 纯函数 </span>
<span class="hljs-comment">// 对于相同的函数，输出是一样的</span>

<span class="hljs-comment">// slice方法，截取的时候返回截取的函数，不影响原数组</span>
numbers.slice(<span class="hljs-number">0</span>, <span class="hljs-number">3</span>) <span class="hljs-comment">// =&gt; [1, 2, 3] </span>
numbers.slice(<span class="hljs-number">0</span>, <span class="hljs-number">3</span>) <span class="hljs-comment">// =&gt; [1, 2, 3] </span>
numbers.slice(<span class="hljs-number">0</span>, <span class="hljs-number">3</span>) <span class="hljs-comment">// =&gt; [1, 2, 3] </span>

<span class="hljs-comment">// 不纯的函数 </span>
<span class="hljs-comment">// 对于相同的输入，输出是不一样的</span>

<span class="hljs-comment">// splice方法，返回原数组，改变原数组</span>
numbers.splice(<span class="hljs-number">0</span>, <span class="hljs-number">3</span>) <span class="hljs-comment">// =&gt; [1, 2, 3] </span>
numbers.splice(<span class="hljs-number">0</span>, <span class="hljs-number">3</span>) <span class="hljs-comment">// =&gt; [4, 5] </span>
numbers.splice(<span class="hljs-number">0</span>, <span class="hljs-number">3</span>) <span class="hljs-comment">// =&gt; []</span>

<span class="hljs-comment">// 下面函数也是纯函数 </span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getSum</span> (<span class="hljs-params">n1, n2</span>) </span>{
    <span class="hljs-keyword">return</span> n1 + n2
}
<span class="hljs-built_in">console</span>.log(getSum(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>)) <span class="hljs-comment">// 3</span>
<span class="hljs-built_in">console</span>.log(getSum(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>)) <span class="hljs-comment">// 3</span>
<span class="hljs-built_in">console</span>.log(getSum(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>)) <span class="hljs-comment">// 3</span>
</div></code></pre>
<ul>
<li>函数式编程不会保留计算中间的结果，所以变量是不可变的(无状态的)</li>
<li>我们也可以把一个函数的执行结果交给另一个函数处理</li>
</ul>
<h3 id="lodash%E7%BA%AF%E5%87%BD%E6%95%B0%E7%9A%84%E4%BB%A3%E8%A1%A8">Lodash——纯函数的代表</h3>
<ul>
<li>lodash 是一个纯函数的功能库，提供了模块化、高性能以及一些附加功能。提供了对数组、数字、对象、字符串、函数等操作的一些方法</li>
</ul>
<h4 id="%E4%BD%93%E9%AA%8Clodash">体验Lodash</h4>
<ul>
<li>安装</li>
</ul>
<p>新建文件夹 -&gt; npm init -y -&gt; npm i lodash</p>
<ul>
<li>体验</li>
</ul>
<pre class="hljs"><code><div><span class="hljs-keyword">const</span> _ = <span class="hljs-built_in">require</span>(<span class="hljs-string">'lodash'</span>)

<span class="hljs-keyword">const</span> array = [<span class="hljs-string">'jack'</span>, <span class="hljs-string">'tom'</span>, <span class="hljs-string">'lucy'</span>, <span class="hljs-string">'kate'</span>]

<span class="hljs-comment">// head的别名是first  _.head(array)也可以</span>
<span class="hljs-built_in">console</span>.log(_.first(array)) <span class="hljs-comment">//jack</span>
<span class="hljs-built_in">console</span>.log(_.last(array)) <span class="hljs-comment">//kate</span>

<span class="hljs-built_in">console</span>.log(_.toUpper(_.first(array))) <span class="hljs-comment">//JACK</span>

<span class="hljs-built_in">console</span>.log(_.reverse(array))  <span class="hljs-comment">//[ 'kate', 'lucy', 'tom', 'jack' ]</span>
<span class="hljs-comment">// 数组的翻转不是纯函数，因为会改变原数组。这里的reserve是使用了数组的reverse，所以也不是纯函数</span>

<span class="hljs-keyword">const</span> r = _.each(array, (item, index) =&gt; {
  <span class="hljs-built_in">console</span>.log(item, index)
  <span class="hljs-comment">// kate 0</span>
  <span class="hljs-comment">// lucy 1</span>
  <span class="hljs-comment">// tom 2</span>
  <span class="hljs-comment">// jack 3</span>
})
<span class="hljs-built_in">console</span>.log(r) <span class="hljs-comment">// [ 'kate', 'lucy', 'tom', 'jack' ]</span>
</div></code></pre>
<h3 id="%E7%BA%AF%E5%87%BD%E6%95%B0%E7%9A%84%E5%A5%BD%E5%A4%84">纯函数的好处</h3>
<h4 id="%E5%8F%AF%E7%BC%93%E5%AD%98">可缓存</h4>
<p>因为对于相同的输入始终有相同的结果，那么可以把纯函数的结果缓存起来，可以提高性能。</p>
<pre class="hljs"><code><div><span class="hljs-keyword">const</span> _ = <span class="hljs-built_in">require</span>(<span class="hljs-string">'lodash'</span>)

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getArea</span>(<span class="hljs-params">r</span>) </span>{
  <span class="hljs-built_in">console</span>.log(r)
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Math</span>.PI * r * r
}

<span class="hljs-keyword">let</span> getAreaWithMemory = _.memoize(getArea)
<span class="hljs-built_in">console</span>.log(getAreaWithMemory(<span class="hljs-number">4</span>))
<span class="hljs-built_in">console</span>.log(getAreaWithMemory(<span class="hljs-number">4</span>))
<span class="hljs-built_in">console</span>.log(getAreaWithMemory(<span class="hljs-number">4</span>))
<span class="hljs-comment">// 4</span>
<span class="hljs-comment">// 50.26548245743669</span>
<span class="hljs-comment">// 50.26548245743669</span>
<span class="hljs-comment">// 50.26548245743669</span>

<span class="hljs-comment">// 看到输出的4只执行了一次，因为其结果被缓存下来了</span>
</div></code></pre>
<p>那我们可以模拟一个记忆函数</p>
<pre class="hljs"><code><div><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">memoize</span> (<span class="hljs-params">f</span>) </span>{
  <span class="hljs-keyword">let</span> cache = {}
  <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-comment">// arguments是一个伪数组，所以要进行字符串的转化</span>
    <span class="hljs-keyword">let</span> key = <span class="hljs-built_in">JSON</span>.stringify(<span class="hljs-built_in">arguments</span>)
    <span class="hljs-comment">// 如果缓存中有值就把值赋值，没有值就调用f函数并且把参数传递给它</span>
    cache[key] = cache[key] || f.apply(f,<span class="hljs-built_in">arguments</span>)
    <span class="hljs-keyword">return</span> cache[key]
  }
}

<span class="hljs-keyword">let</span> getAreaWithMemory1 = memoize(getArea)
<span class="hljs-built_in">console</span>.log(getAreaWithMemory1(<span class="hljs-number">4</span>))
<span class="hljs-built_in">console</span>.log(getAreaWithMemory1(<span class="hljs-number">4</span>))
<span class="hljs-built_in">console</span>.log(getAreaWithMemory1(<span class="hljs-number">4</span>))
<span class="hljs-comment">// 4</span>
<span class="hljs-comment">// 50.26548245743669</span>
<span class="hljs-comment">// 50.26548245743669</span>
<span class="hljs-comment">// 50.26548245743669</span>
</div></code></pre>
<h4 id="%E5%8F%AF%E6%B5%8B%E8%AF%95">可测试</h4>
<p>纯函数让测试更加的方便</p>
<h4 id="%E5%B9%B6%E8%A1%8C%E5%A4%84%E7%90%86">并行处理</h4>
<ul>
<li>多线程环境下并行操作共享的内存数据很可能会出现意外情况。纯函数不需要访问共享的内存数据，所以在并行环境下可以任意运行纯函数</li>
<li>虽然JS是单线程，但是ES6以后有一个Web Worker，可以开启一个新线程</li>
</ul>
<h3 id="%E5%89%AF%E4%BD%9C%E7%94%A8">副作用</h3>
<p>副作用就是让一个函数变得不纯，纯函数的根据市相同的输入返回相同的输出，如果函数依赖于外部的状态就无法保证输出相同，就会带来副作用，如下面的例子：</p>
<pre class="hljs"><code><div><span class="hljs-comment">// 不纯的函数，因为它依赖于外部的变量</span>
<span class="hljs-keyword">let</span> mini = <span class="hljs-number">18</span> 
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">checkAge</span> (<span class="hljs-params">age</span>) </span>{ 
    <span class="hljs-keyword">return</span> age &gt;= mini 
}
</div></code></pre>
<p>副作用来源：</p>
<ul>
<li>配置文件</li>
<li>数据库</li>
<li>获取用户的输入</li>
<li>......</li>
</ul>
<p>所有的外部交互都有可能带来副作用，副作用也使得方法通用性下降不适合扩展和可重用性，同时副作用会给程序中带来安全隐患给程序带来不确定性，但是副作用不可能完全禁止，我们不能禁止用户输入用户名和密码，只能尽可能控制它们在可控范围内发生。</p>
<h2 id="%E6%9F%AF%E9%87%8C%E5%8C%96">柯里化</h2>
<p>解决硬编码的问题</p>
<pre class="hljs"><code><div><span class="hljs-comment">// 下面这段代码是解决了不纯的函数的问题，但是里面出现了硬编码</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">checkAge</span> (<span class="hljs-params">age</span>) </span>{ 
    <span class="hljs-keyword">let</span> mini = <span class="hljs-number">18</span>
    <span class="hljs-keyword">return</span> age &gt;= mini 
}


<span class="hljs-comment">// 普通的纯函数</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">checkAge</span> (<span class="hljs-params">min, age</span>) </span>{
    <span class="hljs-keyword">return</span> age &gt;= min
}
<span class="hljs-built_in">console</span>.log(checkAge(<span class="hljs-number">18</span>, <span class="hljs-number">20</span>))  <span class="hljs-comment">//true</span>
<span class="hljs-built_in">console</span>.log(checkAge(<span class="hljs-number">18</span>, <span class="hljs-number">24</span>))  <span class="hljs-comment">//true</span>
<span class="hljs-built_in">console</span>.log(checkAge(<span class="hljs-number">20</span>, <span class="hljs-number">24</span>))  <span class="hljs-comment">//true</span>
<span class="hljs-comment">// 经常使用18，这段代码是重复的。避免重复</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">checkAge</span> (<span class="hljs-params">min</span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">age</span>) </span>{
        <span class="hljs-keyword">return</span> age &gt;= min
    }
}

<span class="hljs-keyword">let</span> checkAge18 = checkAge(<span class="hljs-number">18</span>)
<span class="hljs-keyword">let</span> checkAge20 = checkAge(<span class="hljs-number">20</span>)

<span class="hljs-built_in">console</span>.log(checkAge18(<span class="hljs-number">20</span>)) <span class="hljs-comment">//true</span>
<span class="hljs-built_in">console</span>.log(checkAge18(<span class="hljs-number">24</span>)) <span class="hljs-comment">//true</span>
</div></code></pre>
<p>柯里化：当函数有多个参数的时候，我们可以对函数进行改造。我们可以调用一个函数，只传递部分的参数（这部分参数以后永远不变），然后让这个函数返回一个新的函数。新的函数传递剩余的参数，并且返回相应的结果。</p>
<pre class="hljs"><code><div><span class="hljs-comment">// ES6</span>
<span class="hljs-keyword">let</span> checkAge = <span class="hljs-function"><span class="hljs-params">min</span> =&gt;</span> (<span class="hljs-function"><span class="hljs-params">age</span> =&gt;</span> age &gt;= min)
<span class="hljs-comment">// 输出相同</span>
</div></code></pre>
<h3 id="lodash%E4%B8%AD%E7%9A%84%E6%9F%AF%E9%87%8C%E5%8C%96--curry">Lodash中的柯里化 —— curry()</h3>
<p>_.curry(func)</p>
<ul>
<li>功能：创建一个函数，该函数接收一个或多个 func的参数，如果 func 所需要的参数都被提供则执行 func 并返回执行的结果。否则继续返回该函数并等待接收剩余的参数。</li>
<li>参数：需要柯里化的函数</li>
<li>返回值：柯里化后的函数</li>
</ul>
<pre class="hljs"><code><div><span class="hljs-keyword">const</span> _ = <span class="hljs-built_in">require</span>(<span class="hljs-string">'lodash'</span>)

<span class="hljs-comment">// 参数是一个的为一元函数，两个的是二元函数</span>
<span class="hljs-comment">// 柯里化可以把一个多元函数转化成一元函数</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getSum</span> (<span class="hljs-params">a, b, c</span>) </span>{
  <span class="hljs-keyword">return</span> a + b + c
}

<span class="hljs-comment">// 定义一个柯里化函数</span>
<span class="hljs-keyword">const</span> curried = _.curry(getSum)

<span class="hljs-comment">// 如果输入了全部的参数，则立即返回结果</span>
<span class="hljs-built_in">console</span>.log(curried(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>)) <span class="hljs-comment">// 6</span>

<span class="hljs-comment">//如果传入了部分的参数，此时它会返回当前函数，并且等待接收getSum中的剩余参数</span>
<span class="hljs-built_in">console</span>.log(curried(<span class="hljs-number">1</span>)(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>)) <span class="hljs-comment">// 6</span>
<span class="hljs-built_in">console</span>.log(curried(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>)(<span class="hljs-number">3</span>)) <span class="hljs-comment">// 6</span>
</div></code></pre>
<h3 id="%E6%A1%88%E4%BE%8B">案例</h3>
<p>判断字符串中有没有空白字符，或者提取字符串中所有空白字符，可以使用字符串的match方法:
<code>''.match(/\s+/g)</code></p>
<p>但是我们要是写一个数组的去处空白字符的方法，上面的代码就无法重用。那我们如何用函数式方法去写</p>
<pre class="hljs"><code><div><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">match</span>(<span class="hljs-params">reg, str</span>) </span>{
  <span class="hljs-keyword">return</span> str.match(reg)
}
</div></code></pre>
<p>reg的表达式是重复的，上面的函数如何柯里化，思路是这样的：</p>
<pre class="hljs"><code><div><span class="hljs-comment">//柯里化处理</span>
<span class="hljs-keyword">const</span> _ = <span class="hljs-built_in">require</span>(<span class="hljs-string">'lodash'</span>)

<span class="hljs-comment">//利用lodash的curry函数，第一个参数是匹配规则，第二个参数是字符串，生成一个match函数</span>
<span class="hljs-keyword">const</span> match = _.curry(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">reg, str</span>) </span>{
  <span class="hljs-keyword">return</span> str.match(reg)
})

<span class="hljs-comment">// 根据规则haveSpace是一个匹配空格的函数</span>
<span class="hljs-keyword">const</span> haveSpace = match(<span class="hljs-regexp">/\s+/g</span>)

<span class="hljs-built_in">console</span>.log(haveSpace(<span class="hljs-string">"hello world"</span>)) <span class="hljs-comment">//[ ' ' ]</span>
<span class="hljs-built_in">console</span>.log(haveSpace(<span class="hljs-string">"helloworld"</span>)) <span class="hljs-comment">//null</span>
<span class="hljs-comment">// 由此可以判断字符串里面有没有空格</span>

<span class="hljs-comment">// 那如果是数字的话怎么办呢？</span>
<span class="hljs-comment">// 根据规则haveNumber是一个匹配数字的函数</span>
<span class="hljs-keyword">const</span> haveNumber = match(<span class="hljs-regexp">/\d+/g</span>)
<span class="hljs-built_in">console</span>.log(haveNumber(<span class="hljs-string">'abc'</span>)) <span class="hljs-comment">// null</span>

<span class="hljs-comment">// 对于数组怎么匹配元素中有没有空格</span>
<span class="hljs-keyword">const</span> filter = _.curry(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">func, array</span>) </span>{
  <span class="hljs-keyword">return</span> array.filter(func)
})

<span class="hljs-comment">// filter函数，第一个参数传递匹配元素中有没有空格</span>
<span class="hljs-comment">//第二个参数是指定的数组</span>
<span class="hljs-built_in">console</span>.log(filter(haveSpace, [<span class="hljs-string">'John Connor'</span>,<span class="hljs-string">'John_Donne'</span>])) <span class="hljs-comment">// [ 'John Connor' ]</span>

<span class="hljs-comment">// 如果上述写还是比较麻烦，那么可以再封装一个函数出来</span>
<span class="hljs-comment">// filter可以传一个参数，然后返回一个函数</span>
<span class="hljs-comment">// 这个findSpace就是匹配数组元素中有没有空格的函数</span>
<span class="hljs-keyword">const</span> findSpace = filter(haveSpace)
<span class="hljs-built_in">console</span>.log(findSpace([<span class="hljs-string">'John Connor'</span>,<span class="hljs-string">'John_Donne'</span>])) <span class="hljs-comment">// [ 'John Connor' ]</span>
</div></code></pre>
<p>下面对上面的思路做一个小的<strong>总结</strong>，柯里化的好处就是<strong>我们可以最大程度的重用我们的函数</strong>。</p>
<pre class="hljs"><code><div><span class="hljs-keyword">const</span> _ = <span class="hljs-built_in">require</span>(<span class="hljs-string">'lodash'</span>)

<span class="hljs-comment">//match函数是根据一些正则，匹配字符串，返回匹配结果</span>
<span class="hljs-keyword">const</span> match = _.curry(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">reg, str</span>) </span>{
  <span class="hljs-keyword">return</span> str.match(reg)
})

<span class="hljs-comment">//haveSpace函数是一个匹配空格的函数</span>
<span class="hljs-keyword">const</span> haveSpace = match(<span class="hljs-regexp">/\s+/g</span>)

<span class="hljs-comment">//haveNumber函数是一个匹配数字的函数</span>
<span class="hljs-keyword">const</span> haveNumber = match(<span class="hljs-regexp">/\d+/g</span>)

<span class="hljs-comment">//filter函数是定义一个数组和过滤规则，返回符合匹配规则的数组</span>
<span class="hljs-keyword">const</span> filter = _.curry(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">func, array</span>) </span>{
  <span class="hljs-keyword">return</span> array.filter(func)
})

<span class="hljs-comment">//findSpace函数是匹配数组元素中有空格并返回符合情况的数组的函数</span>
<span class="hljs-keyword">const</span> findSpace = filter(haveSpace)
</div></code></pre>
<h3 id="%E6%9F%AF%E9%87%8C%E5%8C%96%E5%8E%9F%E7%90%86%E6%A8%A1%E6%8B%9F">柯里化原理模拟</h3>
<p>我们找一个之前做过的例子分析一下</p>
<pre class="hljs"><code><div><span class="hljs-keyword">const</span> _ = <span class="hljs-built_in">require</span>(<span class="hljs-string">'lodash'</span>)

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getSum</span> (<span class="hljs-params">a, b, c</span>) </span>{
  <span class="hljs-keyword">return</span> a + b + c
}

<span class="hljs-keyword">const</span> curried = _.curry(getSum)

<span class="hljs-built_in">console</span>.log(curried(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>))  <span class="hljs-comment">// 6</span>
<span class="hljs-built_in">console</span>.log(curried(<span class="hljs-number">1</span>)(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>))  <span class="hljs-comment">// 6</span>
<span class="hljs-built_in">console</span>.log(curried(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>)(<span class="hljs-number">3</span>))  <span class="hljs-comment">// 6</span>
</div></code></pre>
<p>实现一个柯里化转换函数要进行分析</p>
<ol>
<li>入参出参：调用传递一个纯函数的参数，完成之后返回一个柯里化函数</li>
<li>入参情况分析：</li>
</ol>
<ul>
<li>如果curried调用传递的参数和getSum函数参数个数相同，那么立即执行并返回调用结果</li>
<li>如果curried调用传递的参数是getSum函数的部分参数，那么需要返回一个新的函数，并且等待接收getSum的其他参数</li>
</ul>
<ol start="3">
<li>重点关注：</li>
</ol>
<ul>
<li>获取调用的参数</li>
<li>判断个数是否相同</li>
</ul>
<pre class="hljs"><code><div><span class="hljs-comment">// 模拟柯里化函数</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">curry</span> (<span class="hljs-params">func</span>) </span>{
  <span class="hljs-comment">// 取名字是为了下面实参小于形参的时候用的</span>
  <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">curriedFn</span>(<span class="hljs-params">...args</span>) </span>{
    <span class="hljs-comment">// 判断实参和形参的个数</span>
    <span class="hljs-keyword">if</span>(args.length &lt; func.length) {
      <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-comment">// 等待传递的剩余参数，如果剩余函数的参数加上之前的参数等于形参，那么就返回func</span>
        <span class="hljs-comment">// 第一部分参数在args里面，第二部分参数在arguments里面，要将两个合并并且展开传递（使用...）</span>
        <span class="hljs-comment">// concat函数要合并两个数组，arguments为伪数组，所以用Array.from进行转换</span>
        <span class="hljs-keyword">return</span> curriedFn(...args.concat(<span class="hljs-built_in">Array</span>.from(<span class="hljs-built_in">arguments</span>)))
      }
    }
    <span class="hljs-comment">// 如果实参大于等于形参的个数</span>
    <span class="hljs-comment">// args是剩余参数，是个数组形式，而返回的时候要展开（使用...）</span>
    <span class="hljs-keyword">return</span> func(...args)
  }
}


<span class="hljs-comment">// test</span>
<span class="hljs-keyword">const</span> curriedTest = curry(getSum)

<span class="hljs-built_in">console</span>.log(curriedTest(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>))  <span class="hljs-comment">// 6</span>
<span class="hljs-built_in">console</span>.log(curriedTest(<span class="hljs-number">1</span>)(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>))  <span class="hljs-comment">// 6</span>
<span class="hljs-built_in">console</span>.log(curriedTest(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>)(<span class="hljs-number">3</span>))  <span class="hljs-comment">// 6</span>
</div></code></pre>
<h3 id="%E6%9F%AF%E9%87%8C%E5%8C%96%E6%80%BB%E7%BB%93">柯里化总结</h3>
<ul>
<li>柯里化可以让我们给一个函数传递较少的参数<strong>得到一个已经记住了某些固定参数的新函数</strong>（比如match函数新生成了haveSpace函数，里面使用了闭包，记住了我们给传递的正则表达式的参数）</li>
<li>这是一种对函数参数的'缓存'（使用了闭包）</li>
<li>让函数变的更灵活，让函数的粒度更小</li>
<li>可以把多元函数转换成一元函数，可以组合使用函数产生强大的功能</li>
</ul>
<h2 id="%E5%87%BD%E6%95%B0%E7%BB%84%E5%90%88">函数组合</h2>
<h3 id="%E8%83%8C%E6%99%AF%E7%9F%A5%E8%AF%86">背景知识</h3>
<ul>
<li>纯函数和柯里化很容易写出洋葱代码 <code>h(g(f(x)))</code></li>
</ul>
<pre class="hljs"><code><div><span class="hljs-comment">//获取数组的最后一个元素再转换成大写字母</span>
<span class="hljs-comment">//先翻转数据 --&gt; 再取第一个元素 --&gt; 再转换成大写字母</span>
_.toUpper(_.first(_.reverse(array)))
</div></code></pre>
<p>函数组合可以让我们把细粒度的函数重新组合生成一个新的函数，避免写出洋葱代码</p>
<h3 id="%E7%AE%A1%E9%81%93">管道</h3>
<blockquote>
<p>a --&gt; fn --&gt; b</p>
<p>a-&gt; f3 -&gt; m -&gt; f2 -&gt; n -&gt; f1 -&gt; b</p>
<p>其实中间m、n、是什么我们也不关心
类似于下面的函数</p>
</blockquote>
<pre class="hljs"><code><div>fn = compose(f1, f2, f3)
b = fn(a)
</div></code></pre>
<p>函数组合</p>
<ul>
<li><strong>函数组合 (compose)</strong>：如果一个函数要经过多个函数处理才能得到最终值，这个时候可以把中间
过程的函数合并成一个函数</li>
<li>函数组合默认是<strong>从右到左</strong>执行</li>
</ul>
<pre class="hljs"><code><div><span class="hljs-comment">// 函数组合演示</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">compose</span>(<span class="hljs-params">f, g</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">value</span>) </span>{
    <span class="hljs-keyword">return</span> f(g(value))
  }
}

<span class="hljs-comment">// 数组翻转函数</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">reverse</span> (<span class="hljs-params">array</span>) </span>{
  <span class="hljs-keyword">return</span> array.reverse()
}

<span class="hljs-comment">// 获取函数第一个元素函数</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">first</span> (<span class="hljs-params">array</span>) </span>{
  <span class="hljs-keyword">return</span> array[<span class="hljs-number">0</span>]
}

<span class="hljs-comment">// 组合函数，获取函数最后一个元素</span>
<span class="hljs-keyword">const</span> last = compose(first, reverse)

<span class="hljs-built_in">console</span>.log(last([<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>])) <span class="hljs-comment">// 4</span>
</div></code></pre>
<h3 id="lodash%E4%B8%AD%E7%9A%84%E7%BB%84%E5%90%88%E5%87%BD%E6%95%B0--flowflowright">Lodash中的组合函数 —— flow()/flowRight()</h3>
<p>lodash 中组合函数 flow() 或者flowRight()，他们都可以组合多个函数。</p>
<ul>
<li>flow() 是从左到右运行</li>
<li>flowRight() 是从右到左运行，使用的更多一些</li>
</ul>
<p>下面实例是获取数组的最后一个元素并转化成大写字母</p>
<pre class="hljs"><code><div><span class="hljs-keyword">const</span> _ = <span class="hljs-built_in">require</span>(<span class="hljs-string">'lodash'</span>)

<span class="hljs-keyword">const</span> reverse = <span class="hljs-function"><span class="hljs-params">arr</span> =&gt;</span> arr.reverse()
<span class="hljs-keyword">const</span> first = <span class="hljs-function"><span class="hljs-params">arr</span> =&gt;</span> arr[<span class="hljs-number">0</span>]
<span class="hljs-keyword">const</span> toUpper = <span class="hljs-function"><span class="hljs-params">s</span> =&gt;</span> s.toUpperCase()

<span class="hljs-keyword">const</span> f = _.flowRight(toUpper, first, reverse)

<span class="hljs-built_in">console</span>.log(f([<span class="hljs-string">'one'</span>, <span class="hljs-string">'two'</span>, <span class="hljs-string">'three'</span>])) <span class="hljs-comment">// THREE</span>
</div></code></pre>
<h3 id="%E5%87%BD%E6%95%B0%E7%BB%84%E5%90%88%E5%8E%9F%E7%90%86%E6%A8%A1%E6%8B%9F">函数组合原理模拟</h3>
<p>上面的例子我们来分析一下：</p>
<p>入参不固定，参数都是函数，出参是一个函数，这个函数要有一个初始的参数值</p>
<pre class="hljs"><code><div><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">compose</span> (<span class="hljs-params">...args</span>) </span>{
  <span class="hljs-comment">// 返回的函数，有一个传入的初始参数即value</span>
  <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">value</span>) </span>{
    <span class="hljs-comment">// ...args是执行的函数的数组，从右向左执行那么数组要进行reverse翻转</span>
    <span class="hljs-comment">// reduce: 对数组中的每一个元素，去执行我们提供的一个函数，并将其汇总成一个单个结果</span>
    <span class="hljs-comment">// reduce的第一个参数是一个回调函数，第二个参数是acc的初始值，这里acc的初始值就是value</span>

    <span class="hljs-comment">// reduce第一个参数的回调函数需要两个参数，第一个参数是汇总的一个结果，第二个参数是如果处理汇总的结果的函数并返回一个新的值</span>
    <span class="hljs-comment">// fn指的是数组中的每一个元素（即函数），来处理参数acc，完成之后下一个数组元素处理的是上一个数组的结果acc</span>
    <span class="hljs-keyword">return</span> args.reverse().reduce(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">acc, fn</span>) </span>{
      <span class="hljs-keyword">return</span> fn(acc)
    }, value)
  }
}


<span class="hljs-comment">//test</span>
<span class="hljs-keyword">const</span> fTest = compose(toUpper, first, reverse)
<span class="hljs-built_in">console</span>.log(fTest([<span class="hljs-string">'one'</span>, <span class="hljs-string">'two'</span>, <span class="hljs-string">'three'</span>])) <span class="hljs-comment">// THREE</span>


<span class="hljs-comment">// ES6的写法（函数都变成箭头函数）</span>
<span class="hljs-keyword">const</span> compose = <span class="hljs-function">(<span class="hljs-params">...args</span>) =&gt;</span> <span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> args.reverse().reduce(<span class="hljs-function">(<span class="hljs-params">acc, fn</span>) =&gt;</span> fn(acc), value)
</div></code></pre>
<h3 id="%E5%87%BD%E6%95%B0%E7%BB%84%E5%90%88-%E7%BB%93%E5%90%88%E5%BE%8B">函数组合-结合律</h3>
<h4 id="%E4%BB%80%E4%B9%88%E6%98%AF%E5%87%BD%E6%95%B0%E7%BB%84%E5%90%88%E7%BB%93%E5%90%88%E5%BE%8B">什么是函数组合结合律？</h4>
<p>下面三个情况结果一样，我们既可以把 g 和 h 组合，还可以把 f 和 g 组合。</p>
<pre class="hljs"><code><div><span class="hljs-comment">// 结合律（associativity） </span>
<span class="hljs-keyword">let</span> f = compose(f, g, h) 
<span class="hljs-keyword">let</span> associative = compose(compose(f, g), h) == compose(f, compose(g, h)) 
<span class="hljs-comment">// true</span>
</div></code></pre>
<p>下面用之前的例子再详细说一下：</p>
<pre class="hljs"><code><div><span class="hljs-keyword">const</span> _ = <span class="hljs-built_in">require</span>(<span class="hljs-string">'lodash'</span>)

<span class="hljs-comment">// 方式一</span>
<span class="hljs-keyword">const</span> f = _.flowRight(_.toUpper, _.first, _.reverse)
<span class="hljs-comment">// 方式二</span>
<span class="hljs-keyword">const</span> f = _.flowRight(_.flowRight(_.toUpper, _.first), _.reverse)
<span class="hljs-comment">// 方式三</span>
<span class="hljs-keyword">const</span> f = _.flowRight(_.toUpper, _.flowRight(_.first,  _.reverse))

<span class="hljs-comment">// 无论上面那种写法，下面都输出THREE这个相同的结果</span>
<span class="hljs-built_in">console</span>.log(f([<span class="hljs-string">'one'</span>, <span class="hljs-string">'two'</span>, <span class="hljs-string">'three'</span>])) <span class="hljs-comment">// THREE</span>
</div></code></pre>
<h3 id="%E5%87%BD%E6%95%B0%E7%BB%84%E5%90%88-%E8%B0%83%E8%AF%95">函数组合-调试</h3>
<p>如果我们运行的结果和我们的预期不一致，我们怎么调试呢？我们怎么能知道中间运行的结果呢？</p>
<p>下面这个输入<code>NEVER SAY DIE</code>要对应输出<code>nerver-say-die</code></p>
<p>注意：
每次把自己加的参数写前面，传入的值写后面</p>
<pre class="hljs"><code><div><span class="hljs-keyword">const</span> _ = <span class="hljs-built_in">require</span>(<span class="hljs-string">'lodash'</span>)

<span class="hljs-comment">// 这里split函数需要传入两个参数，且我们最后调用的时候要传入字符串，所以字符串要在第二个位置传入，这里我们需要自己封装一个split函数</span>
<span class="hljs-comment">// _.split(string, separator)</span>

<span class="hljs-comment">// 将多个参数转成一个参数，用到函数的柯里化</span>
<span class="hljs-keyword">const</span> split = _.curry(<span class="hljs-function">(<span class="hljs-params">sep, str</span>) =&gt;</span> _.split(str, sep))

<span class="hljs-comment">// 大写变小写，用到toLower()，因为这个函数只有一个参数，所以可以在函数组合中直接使用</span>

<span class="hljs-comment">// 这里join方法也需要两个参数，第一个参数是数组，第二个参数是分隔符，数组也是最后的时候才传递，也需要交换</span>
<span class="hljs-keyword">const</span> join = _.curry(<span class="hljs-function">(<span class="hljs-params">sep, array</span>) =&gt;</span> _.join(array, sep))

<span class="hljs-keyword">const</span> f = _.flowRight(join(<span class="hljs-string">'-'</span>), _.toLower, split(<span class="hljs-string">' '</span>))

<span class="hljs-built_in">console</span>.log(f(<span class="hljs-string">'NEVER SAY DIE'</span>)) <span class="hljs-comment">//n-e-v-e-r-,-s-a-y-,-d-i-e</span>
</div></code></pre>
<p>但是最后的结果却不是我们想要的，那么我们怎么调试呢？</p>
<pre class="hljs"><code><div><span class="hljs-comment">// NEVER SAY DIE --&gt; nerver-say-die</span>

<span class="hljs-keyword">const</span> _ = <span class="hljs-built_in">require</span>(<span class="hljs-string">'lodash'</span>)
 
<span class="hljs-keyword">const</span> split = _.curry(<span class="hljs-function">(<span class="hljs-params">sep, str</span>) =&gt;</span> _.split(str, sep))
<span class="hljs-keyword">const</span> join = _.curry(<span class="hljs-function">(<span class="hljs-params">sep, array</span>) =&gt;</span> _.join(array, sep))

<span class="hljs-comment">// 我们需要对中间值进行打印，并且知道其位置，用柯里化输出一下</span>
<span class="hljs-keyword">const</span> log = _.curry(<span class="hljs-function">(<span class="hljs-params">tag, v</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(tag, v)
  <span class="hljs-keyword">return</span> v
})

<span class="hljs-comment">// 从右往左在每个函数后面加一个log，并且传入tag的值，就可以知道每次结果输出的是什么</span>
<span class="hljs-keyword">const</span> f = _.flowRight(join(<span class="hljs-string">'-'</span>), log(<span class="hljs-string">'after toLower:'</span>), _.toLower, log(<span class="hljs-string">'after split:'</span>), split(<span class="hljs-string">' '</span>))
<span class="hljs-comment">// 从右到左</span>
<span class="hljs-comment">//第一个log：after split: [ 'NEVER', 'SAY', 'DIE' ] 正确</span>
<span class="hljs-comment">//第二个log: after toLower: never,say,die  转化成小写字母的时候，同时转成了字符串，这里出了问题</span>
<span class="hljs-built_in">console</span>.log(f(<span class="hljs-string">'NEVER SAY DIE'</span>)) <span class="hljs-comment">//n-e-v-e-r-,-s-a-y-,-d-i-e</span>


<span class="hljs-comment">// 修改方式，利用数组的map方法，遍历数组的每个元素让其变成小写 </span>
<span class="hljs-comment">// 这里的map需要两个参数，第一个是数组，第二个是回调函数，需要柯里化</span>
<span class="hljs-keyword">const</span> map = _.curry(<span class="hljs-function">(<span class="hljs-params">fn, array</span>) =&gt;</span> _.map(array, fn))

<span class="hljs-keyword">const</span> f1 = _.flowRight(join(<span class="hljs-string">'-'</span>), map(_.toLower), split(<span class="hljs-string">' '</span>))
<span class="hljs-built_in">console</span>.log(f1(<span class="hljs-string">'NEVER SAY DIE'</span>)) <span class="hljs-comment">// never-say-die</span>
</div></code></pre>
<h2 id="fp%E6%A8%A1%E5%9D%97">FP模块</h2>
<p>函数组合的时候用到很多的函数需要柯里化处理，我们每次都处理那些函数有些麻烦，所以lodash中有一个FP模块</p>
<ul>
<li>lodash 的 fp 模块提供了实用的对函数式编程友好的方法</li>
<li>提供了不可变 auto-curried iteratee-first data-last （函数之先，数据之后）的方法</li>
</ul>
<pre class="hljs"><code><div><span class="hljs-comment">// lodash 模块 </span>
<span class="hljs-keyword">const</span> _ = <span class="hljs-built_in">require</span>(<span class="hljs-string">'lodash'</span>)
<span class="hljs-comment">// 数据置先，函数置后</span>
_.map([<span class="hljs-string">'a'</span>, <span class="hljs-string">'b'</span>, <span class="hljs-string">'c'</span>], _.toUpper) 
<span class="hljs-comment">// =&gt; ['A', 'B', 'C'] </span>
_.map([<span class="hljs-string">'a'</span>, <span class="hljs-string">'b'</span>, <span class="hljs-string">'c'</span>]) 
<span class="hljs-comment">// =&gt; ['a', 'b', 'c'] </span>

<span class="hljs-comment">// 数据置先，规则置后</span>
_.split(<span class="hljs-string">'Hello World'</span>, <span class="hljs-string">' '</span>) 

<span class="hljs-comment">//BUT</span>
<span class="hljs-comment">// lodash/fp 模块 </span>
<span class="hljs-keyword">const</span> fp = <span class="hljs-built_in">require</span>(<span class="hljs-string">'lodash/fp'</span>) 

<span class="hljs-comment">// 函数置先，数据置后</span>
fp.map(fp.toUpper, [<span class="hljs-string">'a'</span>, <span class="hljs-string">'b'</span>, <span class="hljs-string">'c'</span>])
fp.map(fp.toUpper)([<span class="hljs-string">'a'</span>, <span class="hljs-string">'b'</span>, <span class="hljs-string">'c'</span>]) 
<span class="hljs-comment">// 规则置先，数据置后</span>
fp.split(<span class="hljs-string">' '</span>, <span class="hljs-string">'Hello World'</span>) 
fp.split(<span class="hljs-string">' '</span>)(<span class="hljs-string">'Hello World'</span>)
</div></code></pre>
<h3 id="%E4%BD%93%E9%AA%8Cfp%E6%A8%A1%E5%9D%97%E5%AF%B9%E4%BA%8E%E7%BB%84%E5%90%88%E5%87%BD%E6%95%B0%E7%9A%84%E5%8F%8B%E5%A5%BD">体验FP模块对于组合函数的友好</h3>
<pre class="hljs"><code><div><span class="hljs-keyword">const</span> fp = <span class="hljs-built_in">require</span>(<span class="hljs-string">'lodash/fp'</span>)

<span class="hljs-keyword">const</span> f = fp.flowRight(fp.join(<span class="hljs-string">'-'</span>), fp.map(fp.toLower), fp.split(<span class="hljs-string">' '</span>))

<span class="hljs-built_in">console</span>.log(f(<span class="hljs-string">'NEVER SAY DIE'</span>)) <span class="hljs-comment">// never-say-die</span>
</div></code></pre>
<h3 id="lodash-map%E6%96%B9%E6%B3%95%E7%9A%84%E5%B0%8F%E9%97%AE%E9%A2%98">Lodash-map方法的小问题</h3>
<pre class="hljs"><code><div><span class="hljs-keyword">const</span> _ = <span class="hljs-built_in">require</span>(<span class="hljs-string">'lodash'</span>)
<span class="hljs-keyword">const</span> fp = <span class="hljs-built_in">require</span>(<span class="hljs-string">'lodash/fp'</span>)

<span class="hljs-built_in">console</span>.log(_.map([<span class="hljs-string">'23'</span>, <span class="hljs-string">'8'</span>, <span class="hljs-string">'10'</span>], <span class="hljs-built_in">parseInt</span>)) 
<span class="hljs-comment">// [ 23, NaN, 2 ]</span>

_.map([<span class="hljs-string">'23'</span>, <span class="hljs-string">'8'</span>, <span class="hljs-string">'10'</span>], <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">...args</span>)</span>{
  <span class="hljs-built_in">console</span>.log(...args)
})
<span class="hljs-comment">// _.map后面的回调函数接受有三个参数，第一个参数是遍历的数组，第二个参数是key/index，第三个参数是对应函数</span>
<span class="hljs-comment">// 23 0 [ '23', '8', '10' ]</span>
<span class="hljs-comment">// 8 1 [ '23', '8', '10' ]</span>
<span class="hljs-comment">// 10 2 [ '23', '8', '10' ]</span>

<span class="hljs-comment">// parseInt第二个参数表示进制，0默认就是10进制，1不存在，2表示2进制，所以输出是那个样子</span>
<span class="hljs-comment">//parseInt('23', 0, array)</span>
<span class="hljs-comment">//parseInt('8', 1, array)</span>
<span class="hljs-comment">//parseInt('10', 2, array)</span>

<span class="hljs-comment">// 要解决的话需要重新封装一个parseInt方法</span>

<span class="hljs-comment">// 而使用fp模块的map方法不存在下面的问题</span>
<span class="hljs-built_in">console</span>.log(fp.map(<span class="hljs-built_in">parseInt</span>, [<span class="hljs-string">'23'</span>, <span class="hljs-string">'8'</span>, <span class="hljs-string">'10'</span>])) 
<span class="hljs-comment">// [ 23, 8, 10 ]</span>
</div></code></pre>
<h2 id="point-free">Point Free</h2>
<p>是一种编程风格，具体的实现是函数的组合。</p>
<p><strong>Point Free：</strong> 我们可以把数据处理的过程定义成与数据无关的合成运算，不需要用到代表数据的那个参数，只要把简单的运算步骤合成到一起，在使用这种模式之前我们需要定义一些辅助的基本运算函数。</p>
<ul>
<li>不需要指明处理的数据</li>
<li>只需要合成运算过程</li>
<li>需要定义一些辅助的基本运算函数</li>
</ul>
<pre class="hljs"><code><div><span class="hljs-comment">//Hello World =&gt; hello world</span>

<span class="hljs-comment">//思路：</span>
<span class="hljs-comment">//先将字母换成小写，然后将空格换成下划线。如果空格比较多，要替换成一个</span>
<span class="hljs-keyword">const</span> fp = <span class="hljs-built_in">require</span>(<span class="hljs-string">'lodash/fp'</span>)

<span class="hljs-comment">// replace方法接收三个参数</span>
<span class="hljs-comment">// 第一个是正则匹配pattern，第二个是匹配后替换的数据，第三个是要传的字符串</span>
<span class="hljs-comment">// 所以这里需要传两个参数</span>
<span class="hljs-keyword">const</span> f = fp.flowRight(fp.replace(<span class="hljs-regexp">/\s+/g</span>, <span class="hljs-string">'_'</span>), fp.toLower)

<span class="hljs-built_in">console</span>.log(f(<span class="hljs-string">'Hello World'</span>)) <span class="hljs-comment">//hello_world</span>
</div></code></pre>
<h3 id="pointfree%E6%A1%88%E4%BE%8B">Pointfree案例</h3>
<pre class="hljs"><code><div><span class="hljs-comment">//world wild web --&gt;W. W. W</span>
<span class="hljs-comment">//思路：</span>
<span class="hljs-comment">//把一个字符串中的额首字母提取并转换成大写，使用. 作为分隔符</span>
<span class="hljs-keyword">const</span> fp = <span class="hljs-built_in">require</span>(<span class="hljs-string">'lodash/fp'</span>)

<span class="hljs-keyword">const</span> firstLetterToUpper = fp.flowRight(fp.join(<span class="hljs-string">'. '</span>), fp.map(fp.first), fp.map(fp.toUpper), fp.split(<span class="hljs-string">' '</span>))
<span class="hljs-built_in">console</span>.log(firstLetterToUpper(<span class="hljs-string">'world wild web'</span>)) <span class="hljs-comment">//W. W. W</span>

<span class="hljs-comment">// 上面的代码进行了两次的遍历，性能较低</span>
<span class="hljs-comment">// 优化</span>
<span class="hljs-keyword">const</span> firstLetterToUpper = fp.flowRight(fp.join(<span class="hljs-string">'. '</span>), fp.map(fp.flowRight(fp.first, fp.toUpper)), fp.split(<span class="hljs-string">' '</span>))
<span class="hljs-built_in">console</span>.log(firstLetterToUpper(<span class="hljs-string">'world wild web'</span>)) <span class="hljs-comment">//W. W. W</span>
</div></code></pre>
<h2 id="functor">Functor</h2>
<h3 id="%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E5%AD%A6%E5%87%BD%E5%AD%90">为什么要学函子？</h3>
<p>函子(representative functor)是范畴论里的概念，指从任意范畴到集合范畴的一种特殊函子。
我们没有办法避免副作用，但是我们尽可能的将副作用控制在可控的范围内，我们可以通过函子去处理副作用，我们也可以通过函子去处理异常，异步操作等。</p>
<h3 id="%E4%BB%80%E4%B9%88%E6%98%AFfunctor">什么是Functor</h3>
<ul>
<li>容器：包含值和值的变形关系(这个变形关系就是函数)</li>
<li>函子：是一个特殊的容器，通过一个普通的对象来实现，该对象具有 map 方法，map方法可以运行一个函数对值进行处理(变形关系)</li>
</ul>
<h3 id="%E7%90%86%E8%A7%A3functor">理解Functor</h3>
<pre class="hljs"><code><div><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Container</span> </span>{
  <span class="hljs-keyword">constructor</span> (value) {
    <span class="hljs-comment">// 这个函子的值是保存在内部的，不对外公布</span>
    <span class="hljs-comment">// _下划线的成员都是私有成员，外部无法访问，值是初始化的传的参数</span>
    <span class="hljs-keyword">this</span>._value = value
  }
  
  <span class="hljs-comment">//有一个对外的方法map，接收一个函数（纯函数），来处理这个值</span>
  map (fn) {
    <span class="hljs-comment">// 返回一个新的函子，把fn处理的值返回给函子，由新的函子来保存</span>
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Container(fn(<span class="hljs-keyword">this</span>._value))
  }
}

<span class="hljs-comment">// 创建一个函子的对象</span>
<span class="hljs-keyword">let</span> r = <span class="hljs-keyword">new</span> Container(<span class="hljs-number">5</span>)
  .map(<span class="hljs-function"><span class="hljs-params">x</span> =&gt;</span> x + <span class="hljs-number">1</span>) <span class="hljs-comment">// 6</span>
  .map(<span class="hljs-function"><span class="hljs-params">x</span> =&gt;</span> x ** <span class="hljs-number">2</span>) <span class="hljs-comment">// 36</span>

<span class="hljs-comment">// 返回了一个container函子对象，里面有值是36，不对外公布</span>
<span class="hljs-built_in">console</span>.log(r) <span class="hljs-comment">//Container { _value: 36 }</span>
</div></code></pre>
<p>上面还是面向对象的编程思想，要修改成函数式编程的思想，需要避免使用new</p>
<pre class="hljs"><code><div><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Container</span> </span>{
  <span class="hljs-comment">//使用类的静态方法，of替代了new Container的作用</span>
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">of</span> (value) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Container(value)
  }
  <span class="hljs-keyword">constructor</span> (value) {
    <span class="hljs-keyword">this</span>._value = value
  }
  
  map (fn) {
    <span class="hljs-keyword">return</span> Container.of(fn(<span class="hljs-keyword">this</span>._value))
  }
}

<span class="hljs-keyword">const</span> r = Container.of(<span class="hljs-number">5</span>)
            .map(<span class="hljs-function"><span class="hljs-params">x</span>=&gt;</span>x+<span class="hljs-number">2</span>) <span class="hljs-comment">// 7</span>
            .map(<span class="hljs-function"><span class="hljs-params">x</span>=&gt;</span> x**<span class="hljs-number">2</span>) <span class="hljs-comment">// 49</span>

<span class="hljs-built_in">console</span>.log(r) <span class="hljs-comment">// Container { _value: 49 }</span>
</div></code></pre>
<h3 id="%E6%80%BB%E7%BB%93">总结</h3>
<ul>
<li>函数式编程的运算不直接操作值，而是由函子完成</li>
<li>函子就是一个实现了 map 契约的对象</li>
<li>我们可以把函子想象成一个盒子，这个盒子里封装了一个值</li>
<li>想要处理盒子中的值，我们需要给盒子的 map 方法传递一个处理值的函数（纯函数），由这个函数来对值进行处理</li>
<li>最终 map 方法返回一个包含新值的盒子（函子）</li>
</ul>
<blockquote>
<p>遗留问题：如果value是null undefined，怎么办？</p>
</blockquote>
<pre class="hljs"><code><div>Container.of(<span class="hljs-literal">null</span>)
  .map(<span class="hljs-function"><span class="hljs-params">x</span>=&gt;</span>x.toUpper) <span class="hljs-comment">// 报错，使得函数不纯</span>
</div></code></pre>
<p>下面会有好几种函子，处理不同的问题</p>
<h3 id="mybe%E5%87%BD%E5%AD%90">MyBe函子</h3>
<p>MayBe 函子的作用就是可以对外部的空值情况做处理（控制副作用在允许的范围）</p>
<pre class="hljs"><code><div><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MayBe</span> </span>{
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">of</span> (value) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> MayBe(value)
  }
  <span class="hljs-keyword">constructor</span> (value) {
    <span class="hljs-keyword">this</span>._value = value
  }

  map(fn) {
    <span class="hljs-comment">// 判断一下value的值是不是null和undefined，如果是就返回一个value为null的函子，如果不是就执行函数</span>
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.isNothing() ? MayBe.of(<span class="hljs-literal">null</span>) : MayBe.of(fn(<span class="hljs-keyword">this</span>._value))
  }

 <span class="hljs-comment">// 定义一个判断是不是null或者undefined的函数，返回true/false</span>
  isNothing() {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>._value === <span class="hljs-literal">null</span> || <span class="hljs-keyword">this</span>._value === <span class="hljs-literal">undefined</span>
  }
}

<span class="hljs-keyword">const</span> r = MayBe.of(<span class="hljs-string">'hello world'</span>)
  .map(<span class="hljs-function"><span class="hljs-params">x</span> =&gt;</span> x.toUpperCase())

<span class="hljs-built_in">console</span>.log(r) <span class="hljs-comment">//MayBe { _value: 'HELLO WORLD' }</span>


<span class="hljs-comment">// 如果输入的是null，是不会报错的</span>
<span class="hljs-keyword">const</span> rnull = MayBe.of(<span class="hljs-literal">null</span>)
  .map(<span class="hljs-function"><span class="hljs-params">x</span> =&gt;</span> x.toUpperCase())
<span class="hljs-built_in">console</span>.log(rnull) <span class="hljs-comment">//MayBe { _value: null }</span>
</div></code></pre>
<p>但是这里有一个问题就是，如果map中间有好几步，最后返回是null，并不知道是哪一个步骤返回的。解决这个问题，需要看下一个函子。</p>
<h3 id="either%E5%87%BD%E5%AD%90">Either函子</h3>
<ul>
<li>Either 两者中的任何一个，类似于 if...else...的处理</li>
<li>当出现问题的时候，Either函子会给出提示的有效信息，</li>
<li>异常会让函数变的不纯，Either 函子可以用来做异常处理</li>
</ul>
<pre class="hljs"><code><div><span class="hljs-comment">// 因为是二选一，所以要定义left和right两个函子</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Left</span> </span>{
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">of</span> (value) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Left(value)
  }

  <span class="hljs-keyword">constructor</span> (value) {
    <span class="hljs-keyword">this</span>._value = value
  }

  map (fn) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>
  }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Right</span> </span>{
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">of</span> (value) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Right(value)
  }

  <span class="hljs-keyword">constructor</span> (value) {
    <span class="hljs-keyword">this</span>._value = value
  }

  map (fn) {
    <span class="hljs-keyword">return</span> Right.of(fn(<span class="hljs-keyword">this</span>._value))
  }
}

<span class="hljs-keyword">let</span> r1 = Right.of(<span class="hljs-number">12</span>).map(<span class="hljs-function"><span class="hljs-params">x</span> =&gt;</span> x + <span class="hljs-number">2</span>)
<span class="hljs-keyword">let</span> r2 = Left.of(<span class="hljs-number">12</span>).map(<span class="hljs-function"><span class="hljs-params">x</span> =&gt;</span> x + <span class="hljs-number">2</span>)
<span class="hljs-built_in">console</span>.log(r1) <span class="hljs-comment">// Right { _value: 14 }</span>
<span class="hljs-built_in">console</span>.log(r2) <span class="hljs-comment">// Left { _value: 12 }</span>
<span class="hljs-comment">// 为什么结果会不一样？因为Left返回的是当前对象，并没有使用fn函数</span>

<span class="hljs-comment">// 那么这里如何处理异常呢？</span>
<span class="hljs-comment">// 我们定义一个字符串转换成对象的函数</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parseJSON</span>(<span class="hljs-params">str</span>) </span>{
  <span class="hljs-comment">// 对于可能出错的环节使用try-catch</span>
  <span class="hljs-comment">// 正常情况使用Right函子</span>
  <span class="hljs-keyword">try</span>{
    <span class="hljs-keyword">return</span> Right.of(<span class="hljs-built_in">JSON</span>.parse(str))
  }<span class="hljs-keyword">catch</span> (e) {
  <span class="hljs-comment">// 错误之后使用Left函子，并返回错误信息</span>
    <span class="hljs-keyword">return</span> Left.of({ <span class="hljs-attr">error</span>: e.message })
  }
}

<span class="hljs-keyword">let</span> rE = parseJSON(<span class="hljs-string">'{name:xm}'</span>)
<span class="hljs-built_in">console</span>.log(rE) <span class="hljs-comment">// Left { _value: { error: 'Unexpected token n in JSON at position 1' } }</span>
<span class="hljs-keyword">let</span> rR = parseJSON(<span class="hljs-string">'{"name":"xm"}'</span>)
<span class="hljs-built_in">console</span>.log(rR) <span class="hljs-comment">// Right { _value: { name: 'xm' } }</span>

<span class="hljs-built_in">console</span>.log(rR.map(<span class="hljs-function"><span class="hljs-params">x</span> =&gt;</span> x.name.toUpperCase())) <span class="hljs-comment">// Right { _value: 'XM' }</span>
</div></code></pre>
<h3 id="io%E5%87%BD%E5%AD%90">IO函子</h3>
<ul>
<li>IO就是输入输出，IO 函子中的 _value 是一个函数，这里是把函数作为值来处理</li>
<li>IO 函子可以把不纯的动作存储到 _value 中，延迟执行这个不纯的操作(惰性执行)，包装当前的操
作</li>
<li>把不纯的操作交给调用者来处理</li>
</ul>
<p>因为IO函数需要用到组合函数，所以需要提前安装Lodash</p>
<blockquote>
<p>npm init -y</p>
</blockquote>
<blockquote>
<p>npm i lodash</p>
</blockquote>
<pre class="hljs"><code><div><span class="hljs-keyword">const</span> fp = <span class="hljs-built_in">require</span>(<span class="hljs-string">'lodash/fp'</span>)

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">IO</span> </span>{
  <span class="hljs-comment">// of方法快速创建IO，要一个值返回一个函数，将来需要值的时候再调用函数</span>
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">of</span>(value) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> IO(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> value)
  }
  <span class="hljs-comment">// 传入的是一个函数</span>
  <span class="hljs-keyword">constructor</span> (fn) {
    <span class="hljs-keyword">this</span>._value = fn
  }

  map(fn) {
    <span class="hljs-comment">// 这里用的是new一个新的构造函数，是为了把当前_value的函数和map传入的fn进行组合成新的函数</span>
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> IO(fp.flowRight(fn, <span class="hljs-keyword">this</span>._value))
  }
}


<span class="hljs-comment">// test</span>
<span class="hljs-comment">// node执行环境可以传一个process对象（进程）</span>
<span class="hljs-comment">// 调用of的时候把当前取值的过程包装到函数里面，再在需要的时候再获取process</span>
<span class="hljs-keyword">const</span> r = IO.of(process)
  <span class="hljs-comment">// map需要传入一个函数，函数需要接收一个参数，这个参数就是of中传递的参数process</span>
  <span class="hljs-comment">// 返回一下process中的execPath属性即当前node进程的执行路径</span>
  .map(<span class="hljs-function"><span class="hljs-params">p</span> =&gt;</span> p.execPath)
<span class="hljs-built_in">console</span>.log(r) <span class="hljs-comment">// IO { _value: [Function] }</span>


<span class="hljs-comment">// 上面只是组合函数，如果需要调用就执行下面</span>
<span class="hljs-built_in">console</span>.log(r._value()) <span class="hljs-comment">// C:\Program Files\nodejs\node.exe</span>
</div></code></pre>
<h3 id="task%E5%87%BD%E5%AD%90%E5%BC%82%E6%AD%A5%E6%89%A7%E8%A1%8C">Task函子（异步执行）</h3>
<ul>
<li>函子可以控制副作用，还可以处理异步任务，为了避免地狱之门。</li>
<li>异步任务的实现过于复杂，我们使用 folktale 中的 Task 来演示</li>
<li><a href="https://folktale.origamitower.com/">folktale</a> 一个标准的函数式编程库。和 lodash、ramda 不同的是，他没有提供很多功能函数。只提供了一些函数式处理的操作，例如：compose、curry 等，一些函子 Task、Either、 MayBe 等</li>
</ul>
<h4 id="folktale%E7%9A%84%E5%AE%89%E8%A3%85">folktale的安装</h4>
<p>首先安装folktale的库</p>
<pre class="hljs"><code><div>npm i folktale
</div></code></pre>
<h4 id="folktale%E4%B8%AD%E7%9A%84curry%E5%87%BD%E6%95%B0">folktale中的curry函数</h4>
<pre class="hljs"><code><div><span class="hljs-keyword">const</span> { compose, curry } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'folktale/core/lambda'</span>)

<span class="hljs-comment">// curry中的第一个参数是函数有几个参数，为了避免一些错误</span>
<span class="hljs-keyword">const</span> f = curry(<span class="hljs-number">2</span>, (x, y) =&gt; x + y)

<span class="hljs-built_in">console</span>.log(f(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>)) <span class="hljs-comment">// 3</span>
<span class="hljs-built_in">console</span>.log(f(<span class="hljs-number">1</span>)(<span class="hljs-number">2</span>)) <span class="hljs-comment">// 3</span>
</div></code></pre>
<h4 id="folktale%E4%B8%AD%E7%9A%84compose%E5%87%BD%E6%95%B0">folktale中的compose函数</h4>
<pre class="hljs"><code><div><span class="hljs-keyword">const</span> { compose, curry } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'folktale/core/lambda'</span>)
<span class="hljs-keyword">const</span> { toUpper, first } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'lodash/fp'</span>)

<span class="hljs-comment">// compose 组合函数在lodash里面是flowRight</span>
<span class="hljs-keyword">const</span> r = compose(toUpper, first)
<span class="hljs-built_in">console</span>.log(r([<span class="hljs-string">'one'</span>, <span class="hljs-string">'two'</span>]))  <span class="hljs-comment">// ONE</span>
</div></code></pre>
<h4 id="task%E5%87%BD%E5%AD%90%E5%BC%82%E6%AD%A5%E6%89%A7%E8%A1%8C">Task函子异步执行</h4>
<ul>
<li>folktale(2.3.2) 2.x 中的 Task 和 1.0 中的 Task 区别很大，1.0 中的用法更接近我们现在演示的
函子</li>
<li>这里以 2.3.2 来演示</li>
</ul>
<pre class="hljs"><code><div><span class="hljs-keyword">const</span> { task } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'folktale/concurrency/task'</span>)
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>)
<span class="hljs-comment">// 2.0中是一个函数，函数返回一个函子对象</span>
<span class="hljs-comment">// 1.0中是一个类</span>

<span class="hljs-comment">//读取文件</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">readFile</span> (<span class="hljs-params">filename</span>) </span>{
  <span class="hljs-comment">// task传递一个函数，参数是resolver</span>
  <span class="hljs-comment">// resolver里面有两个参数，一个是reject失败的时候执行的，一个是resolve成功的时候执行的</span>
  <span class="hljs-keyword">return</span> task(<span class="hljs-function"><span class="hljs-params">resolver</span> =&gt;</span> {
    <span class="hljs-comment">//node中读取文件，第一个参数是路径，第二个是编码，第三个是回调，错误在先</span>
    fs.readFile(filename, <span class="hljs-string">'utf-8'</span>, (err, data) =&gt; {
      <span class="hljs-keyword">if</span>(err) resolver.reject(err)
      resolver.resolve(data)
    })
  })
}

<span class="hljs-comment">//演示一下调用</span>
<span class="hljs-comment">// readFile调用返回的是Task函子，调用要用run方法</span>
readFile(<span class="hljs-string">'package.json'</span>)
  .run()
  <span class="hljs-comment">// 现在没有对resolve进行处理，可以使用task的listen去监听获取的结果</span>
  <span class="hljs-comment">// listen传一个对象，onRejected是监听错误结果，onResolved是监听正确结果</span>
  .listen({
    <span class="hljs-attr">onRejected</span>: <span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(err)
    },
    <span class="hljs-attr">onResolved</span>: <span class="hljs-function">(<span class="hljs-params">value</span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(value)
    }
  })
 
 <span class="hljs-comment">/** {
    "name": "Functor",
    "version": "1.0.0",
    "description": "",
    "main": "either.js",
    "scripts": {
      "test": "echo \"Error: no test specified\" &amp;&amp; exit 1"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "dependencies": {
      "folktale": "^2.3.2",
      "lodash": "^4.17.20"
    }
  }
  */</span>
</div></code></pre>
<h4 id="%E6%A1%88%E4%BE%8B">案例</h4>
<p>在package.json文件中提取一下version字段</p>
<pre class="hljs"><code><div><span class="hljs-keyword">const</span> { task } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'folktale/concurrency/task'</span>)
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>)
<span class="hljs-keyword">const</span> { split, find } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'lodash/fp'</span>)
<span class="hljs-comment">// 2.0中是一个函数，函数返回一个函子对象</span>
<span class="hljs-comment">// 1.0中是一个类</span>

<span class="hljs-comment">//读取文件</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">readFile</span> (<span class="hljs-params">filename</span>) </span>{
  <span class="hljs-comment">// task传递一个函数，参数是resolver</span>
  <span class="hljs-comment">// resolver里面有两个参数，一个是reject失败的时候执行的，一个是resolve成功的时候执行的</span>
  <span class="hljs-keyword">return</span> task(<span class="hljs-function"><span class="hljs-params">resolver</span> =&gt;</span> {
    <span class="hljs-comment">//node中读取文件，第一个参数是路径，第二个是编码，第三个是回调，错误在先</span>
    fs.readFile(filename, <span class="hljs-string">'utf-8'</span>, (err, data) =&gt; {
      <span class="hljs-keyword">if</span>(err) resolver.reject(err)
      resolver.resolve(data)
    })
  })
}

<span class="hljs-comment">//演示一下调用</span>
<span class="hljs-comment">// readFile调用返回的是Task函子，调用要用run方法</span>
readFile(<span class="hljs-string">'package.json'</span>)
  <span class="hljs-comment">//在run之前调用map方法，在map方法中会处理的拿到文件返回结果</span>
  <span class="hljs-comment">// 在使用函子的时候就没有必要想的实现机制</span>
  .map(split(<span class="hljs-string">'\n'</span>))
  .map(find(<span class="hljs-function"><span class="hljs-params">x</span> =&gt;</span> x.includes(<span class="hljs-string">'version'</span>)))
  .run()
  <span class="hljs-comment">// 现在没有对resolve进行处理，可以使用task的listen去监听获取的结果</span>
  <span class="hljs-comment">// listen传一个对象，onRejected是监听错误结果，onResolved是监听正确结果</span>
  .listen({
    <span class="hljs-attr">onRejected</span>: <span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(err)
    },
    <span class="hljs-attr">onResolved</span>: <span class="hljs-function">(<span class="hljs-params">value</span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(value) <span class="hljs-comment">// "version": "1.0.0",</span>
    }
  })

</div></code></pre>
<h3 id="pointed%E5%87%BD%E5%AD%90">Pointed函子</h3>
<ul>
<li>Pointed 函子是实现了 of 静态方法的函子
of 方法是为了避免使用 new 来创建对象，更深层的含义是<strong>of 方法用来把值放到上下文</strong></li>
<li>Context（把值放到容器中，使用 map 来处理值）</li>
</ul>
<pre class="hljs"><code><div><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Container</span> </span>{ 
<span class="hljs-comment">// Point函子</span>
<span class="hljs-comment">// 作用是把值放到一个新的函子里面返回，返回的函子就是一个上下文</span>
    <span class="hljs-keyword">static</span> <span class="hljs-keyword">of</span> (value) { 
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Container(value)
    }
    ……  
}

<span class="hljs-comment">// 调用of的时候获得一个上下文，之后是在上下文中处理数据</span>
Contanier.of(<span class="hljs-number">2</span>)
 .map(<span class="hljs-function"><span class="hljs-params">x</span> =&gt;</span> x + <span class="hljs-number">5</span>)
</div></code></pre>
<h3 id="monad%E5%87%BD%E5%AD%90%E5%8D%95%E5%AD%90">Monad函子（单子）</h3>
<h4 id="io%E5%87%BD%E5%AD%90%E7%9A%84%E5%B5%8C%E5%A5%97%E9%97%AE%E9%A2%98">IO函子的嵌套问题</h4>
<ul>
<li>用来解决IO函子多层嵌套的一个问题</li>
</ul>
<pre class="hljs"><code><div><span class="hljs-keyword">const</span> fp = <span class="hljs-built_in">require</span>(<span class="hljs-string">'lodash/fp'</span>)
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>)

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">IO</span> </span>{
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">of</span> (value) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> IO(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
      <span class="hljs-keyword">return</span> value
    })
  }
  <span class="hljs-keyword">constructor</span> (fn) {
    <span class="hljs-keyword">this</span>._value = fn
  }

  map(fn) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> IO(fp.flowRight(fn, <span class="hljs-keyword">this</span>._value))
  }
}

<span class="hljs-comment">//读取文件函数</span>
<span class="hljs-keyword">let</span> readFile = <span class="hljs-function">(<span class="hljs-params">filename</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> IO(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
    <span class="hljs-comment">//同步获取文件</span>
    <span class="hljs-keyword">return</span> fs.readFileSync(filename, <span class="hljs-string">'utf-8'</span>)
  })
}

<span class="hljs-comment">//打印函数</span>
<span class="hljs-comment">// x是上一步的IO函子</span>
<span class="hljs-keyword">let</span> print = <span class="hljs-function">(<span class="hljs-params">x</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> IO(<span class="hljs-function"><span class="hljs-params">()</span>=&gt;</span> {
    <span class="hljs-built_in">console</span>.log(x)
    <span class="hljs-keyword">return</span> x
  })
}

<span class="hljs-comment">// 组合函数，先读文件再打印</span>
<span class="hljs-keyword">let</span> cat = fp.flowRight(print, readFile)
<span class="hljs-comment">// 调用</span>
<span class="hljs-comment">// 拿到的结果是嵌套的IO函子 IO(IO(x))</span>
<span class="hljs-keyword">let</span> r = cat(<span class="hljs-string">'package.json'</span>)
<span class="hljs-built_in">console</span>.log(r) 
<span class="hljs-comment">// IO { _value: [Function] }</span>
<span class="hljs-built_in">console</span>.log(cat(<span class="hljs-string">'package.json'</span>)._value()) 
<span class="hljs-comment">// IO { _value: [Function] }</span>
<span class="hljs-comment">// IO { _value: [Function] }</span>
<span class="hljs-built_in">console</span>.log(cat(<span class="hljs-string">'package.json'</span>)._value()._value())
<span class="hljs-comment">// IO { _value: [Function] }</span>
<span class="hljs-comment">/**
 * {
  "name": "Functor",
  "version": "1.0.0",
  "description": "",
  "main": "either.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" &amp;&amp; exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "folktale": "^2.3.2",
    "lodash": "^4.17.20"
  }
}
 */</span>
</div></code></pre>
<p>上面遇到多个IO函子嵌套的时候，那么_value就会调用很多次，这样的调用体验很不好。所以进行优化。</p>
<h4 id="%E4%BB%80%E4%B9%88%E6%98%AFmonad%E5%87%BD%E5%AD%90">什么是Monad函子</h4>
<ul>
<li>Monad 函子是可以变扁的 Pointed 函子，用来解决IO函子嵌套问题，IO(IO(x))</li>
<li>一个函子如果具有 join 和 of 两个方法并遵守一些定律就是一个 Monad</li>
</ul>
<h4 id="%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AAmonad%E5%87%BD%E5%AD%90">实现一个Monad函子</h4>
<p>实际开发中不会这么难，主要是知道monad的实现</p>
<pre class="hljs"><code><div><span class="hljs-keyword">const</span> fp = <span class="hljs-built_in">require</span>(<span class="hljs-string">'lodash/fp'</span>)
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>)

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">IO</span> </span>{
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">of</span> (value) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> IO(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
      <span class="hljs-keyword">return</span> value
    })
  }
  <span class="hljs-keyword">constructor</span> (fn) {
    <span class="hljs-keyword">this</span>._value = fn
  }

  map(fn) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> IO(fp.flowRight(fn, <span class="hljs-keyword">this</span>._value))
  }

  join () {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>._value()
  }

  <span class="hljs-comment">// 同时调用map和join方法</span>
  flatMap (fn) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.map(fn).join()
  }
}

<span class="hljs-keyword">let</span> readFile = <span class="hljs-function">(<span class="hljs-params">filename</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> IO(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
    <span class="hljs-keyword">return</span> fs.readFileSync(filename, <span class="hljs-string">'utf-8'</span>)
  })
}

<span class="hljs-keyword">let</span> print = <span class="hljs-function">(<span class="hljs-params">x</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> IO(<span class="hljs-function"><span class="hljs-params">()</span>=&gt;</span> {
    <span class="hljs-built_in">console</span>.log(x)
    <span class="hljs-keyword">return</span> x
  })
}

<span class="hljs-keyword">let</span> r = readFile(<span class="hljs-string">'package.json'</span>)
          .flatMap(print)
          .join()     
<span class="hljs-comment">// 执行顺序</span>
<span class="hljs-comment">/**
 * readFile读取了文件，然后返回了一个IO函子
 * 调用flatMap是用readFile返回的IO函子调用的
 * 并且传入了一个print函数参数
 * 调用flatMap的时候，内部先调用map，当前的print和this._value进行合并，合并之后返回了一个新的函子
 * （this._value就是readFile返回IO函子的函数：
 *      () =&gt; {
          return fs.readFileSync(filename, 'utf-8')
        }
 * ）
 * flatMap中的map函数执行完，print函数返回的一个IO函子，里面包裹的还是一个IO函子
 * 下面调用join函数，join函数就是调用返回的新函子内部的this._value()函数
 * 这个this._value就是之前print和this._value的组合函数，调用之后返回的就是print的返回结果
 * 所以flatMap执行完毕之后，返回的就是print函数返回的IO函子
 *  */</span>
 
 r = readFile(<span class="hljs-string">'package.json'</span>)
        <span class="hljs-comment">// 处理数据，直接在读取文件之后，使用map进行处理即可</span>
        .map(fp.toUpper)
        .flatMap(print)
        .join()  

<span class="hljs-comment">// 读完文件之后想要处理数据，怎么办？</span>
<span class="hljs-comment">// 直接在读取文件之后调用map方法即可</span>

<span class="hljs-comment">/**
 * {
  "NAME": "FUNCTOR",
  "VERSION": "1.0.0",
  "DESCRIPTION": "",
  "MAIN": "EITHER.JS",
  "SCRIPTS": {
    "TEST": "ECHO \"ERROR: NO TEST SPECIFIED\" &amp;&amp; EXIT 1"
  },
  "KEYWORDS": [],
  "AUTHOR": "",
  "LICENSE": "ISC",
  "DEPENDENCIES": {
    "FOLKTALE": "^2.3.2",
    "LODASH": "^4.17.20"
  }
}
 */</span>
</div></code></pre>
<h4 id="monad%E5%87%BD%E5%AD%90%E5%B0%8F%E7%BB%93">Monad函子小结</h4>
<h5 id="%E4%BB%80%E4%B9%88%E6%98%AFmonad">什么是Monad？</h5>
<p>具有静态的IO方法和join方法的函子</p>
<h5 id="%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E4%BD%BF%E7%94%A8monad">什么时候使用Monad?</h5>
<ul>
<li>当一个函数返回一个函子的时候，我们就要想到monad，monad可以帮我们解决函子嵌套的问题。</li>
<li>当我们想要返回一个函数，这个函数返回一个值，这个时候可以调用<strong>map</strong> 方法</li>
<li>当我们想要去合并一个函数，但是这个函数返回一个函子，这个时候我们要用<strong>flatMap</strong> 方法</li>
</ul>
<h2 id="%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%E6%80%BB%E7%BB%93">函数式编程总结</h2>
<pre class="hljs"><code><div>graph LR
函数式编程--&gt;认识函数式编程
函数式编程--&gt;函数相关复习
函数相关复习--&gt;函数是一等公民
函数相关复习--&gt;高阶函数
函数相关复习--&gt;闭包
函数式编程--&gt;函数式编程基础
函数式编程基础--&gt;lodash
函数式编程基础--&gt;纯函数
函数式编程基础--&gt;柯里化
函数式编程基础--&gt;管道
函数式编程基础--&gt;函数组合
函数式编程--&gt;函子
函子--&gt;Functor
函子--&gt;MayBe
函子--&gt;Either
函子--&gt;IO
函子--&gt;Task
Task--&gt;folktale
函子--&gt;Monad
</div></code></pre>
<h2 id="%E9%99%84%E5%BD%95">附录</h2>
<ul>
<li><a href="https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/ch1.html">函数式编程指北</a></li>
<li><a href="http://www.ruanyifeng.com/blog/2017/02/fp-tutorial.html">函数式编程入门</a></li>
<li><a href="http://www.ruanyifeng.com/blog/2017/03/pointfree.html">Pointfree 编程风格指南</a></li>
<li><a href="http://www.ruanyifeng.com/blog/2015/07/monad.html">图解 Monad</a></li>
<li><a href="http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html">Functors, Applicatives, And Monads In Pictures</a></li>
</ul>

</body>
</html>
